﻿#include "myopenglwidget.h"

unsigned int VAO, VBO, EBO;

float vertices[] = {
    -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, //
    0.5f,  -0.5f, -0.5f, 1.0f, 0.0f, //
    0.5f,  0.5f,  -0.5f, 1.0f, 1.0f, //
    0.5f,  0.5f,  -0.5f, 1.0f, 1.0f, //
    -0.5f, 0.5f,  -0.5f, 0.0f, 1.0f, //
    -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, //

    -0.5f, -0.5f, 0.5f,  0.0f, 0.0f, //
    0.5f,  -0.5f, 0.5f,  1.0f, 0.0f, //
    0.5f,  0.5f,  0.5f,  1.0f, 1.0f, //
    0.5f,  0.5f,  0.5f,  1.0f, 1.0f, //
    -0.5f, 0.5f,  0.5f,  0.0f, 1.0f, //
    -0.5f, -0.5f, 0.5f,  0.0f, 0.0f, //

    -0.5f, 0.5f,  0.5f,  1.0f, 0.0f, //
    -0.5f, 0.5f,  -0.5f, 1.0f, 1.0f, //
    -0.5f, -0.5f, -0.5f, 0.0f, 1.0f, //
    -0.5f, -0.5f, -0.5f, 0.0f, 1.0f, //
    -0.5f, -0.5f, 0.5f,  0.0f, 0.0f, //
    -0.5f, 0.5f,  0.5f,  1.0f, 0.0f, //

    0.5f,  0.5f,  0.5f,  1.0f, 0.0f, //
    0.5f,  0.5f,  -0.5f, 1.0f, 1.0f, //
    0.5f,  -0.5f, -0.5f, 0.0f, 1.0f, //
    0.5f,  -0.5f, -0.5f, 0.0f, 1.0f, //
    0.5f,  -0.5f, 0.5f,  0.0f, 0.0f, //
    0.5f,  0.5f,  0.5f,  1.0f, 0.0f, //

    -0.5f, -0.5f, -0.5f, 0.0f, 1.0f, //
    0.5f,  -0.5f, -0.5f, 1.0f, 1.0f, //
    0.5f,  -0.5f, 0.5f,  1.0f, 0.0f, //
    0.5f,  -0.5f, 0.5f,  1.0f, 0.0f, //
    -0.5f, -0.5f, 0.5f,  0.0f, 0.0f, //
    -0.5f, -0.5f, -0.5f, 0.0f, 1.0f, //

    -0.5f, 0.5f,  -0.5f, 0.0f, 1.0f, //
    0.5f,  0.5f,  -0.5f, 1.0f, 1.0f, //
    0.5f,  0.5f,  0.5f,  1.0f, 0.0f, //
    0.5f,  0.5f,  0.5f,  1.0f, 0.0f, //
    -0.5f, 0.5f,  0.5f,  0.0f, 0.0f, //
    -0.5f, 0.5f,  -0.5f, 0.0f, 1.0f, //
};

QVector<QVector3D> cubePositions = {
    QVector3D( 0.0f, 0.0f, 0.0f ),     //
    QVector3D( 2.0f, 5.0f, -15.0f ),   //
    QVector3D( -1.5f, -2.2f, -2.5f ),  //
    QVector3D( -3.8f, -2.0f, -12.3f ), //
    QVector3D( 2.4f, -0.4f, -3.5f ),   //
    QVector3D( -1.7f, 3.0f, -7.5f ),   //
    QVector3D( 1.3f, -2.0f, -2.5f ),   //
    QVector3D( 1.5f, 2.0f, -2.5f ),    //
    QVector3D( 1.5f, 0.2f, -1.5f ),    //
    QVector3D( -1.3f, 1.0f, -1.5f )    //
};

unsigned int indices[] = {
    0, 1, 3, //
    1, 2, 3  //
};           // 需要画的点

float ratio = 0.5;

MyOpenGLWidget::MyOpenGLWidget( QWidget *parent )
    : QOpenGLWidget( parent ) {
    setFocusPolicy( Qt::StrongFocus );
    setMouseTracking( true );
    connect( &m_timer, SIGNAL( timeout() ), this, SLOT( on_timerout() ) );
    m_timer.start( 100 );

    m_time.start();

    // Camera position
    cameraPos = QVector3D( 0.0f, 0.0f, 2.0f );
    // Camera direction
    cameraTarget    = QVector3D( 0.0f, 0.0f, 0.0f );
    cameraDirection = QVector3D( cameraPos - cameraTarget );
    cameraDirection.normalize();
    // Right axis
    up          = QVector3D( 0.0f, 1.0f, 0.0f );
    cameraRight = QVector3D::crossProduct( up, cameraDirection );
    cameraRight.normalize();
    // up axis
    cameraUp    = QVector3D::crossProduct( cameraDirection, cameraRight );
    cameraFront = QVector3D( 0.0f, 0.0f, -1.0f );

    fov = 45.0f;
}

MyOpenGLWidget::~MyOpenGLWidget() {
    makeCurrent(); // 获取当前状态
    glDeleteBuffers( 1, &VBO );
    glDeleteBuffers( 1, &EBO );
    glDeleteVertexArrays( 1, &VAO ); // 删除顶底和着色器
                                     //    glDeleteShader( shaderProgram );
    doneCurrent();                   // 退出当前状态
}

void MyOpenGLWidget::drawShape( Shape shape ) {
    m_shape = shape;
    update();
}

void MyOpenGLWidget::setWireframe( bool wireframe ) {
    makeCurrent();
    if ( wireframe ) {
        glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
    } else {
        glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
    }
    update();
    doneCurrent();
}

void MyOpenGLWidget::initializeGL() {
    initializeOpenGLFunctions(); // 本地的指针初始化

    // 创建VAO VBO，并赋予对象ID
    glGenVertexArrays( 1, &VAO );
    glGenBuffers( 1, &VBO );

    // 一次创建多个VAO,VBO
    glBindVertexArray( VAO );
    glBindBuffer( GL_ARRAY_BUFFER, VBO );

    // 为当前绑定到target的缓冲区对象创建一个新的数据存储
    // 如果data不是null，则使用来自此指针的数据初始化数据存储
    glBufferData( GL_ARRAY_BUFFER, sizeof( vertices ), vertices, GL_STATIC_DRAW );

    // 告诉显卡如何解析缓冲区的属性值
    glVertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof( float ), (void *) 0 );
    // 开启一个VAO管理的第一个属性值
    glEnableVertexAttribArray( 0 );

    // 告诉显卡如何解析缓冲区的属性值
    glVertexAttribPointer( 1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof( float ), (void *) ( 3 * sizeof( float ) ) );
    // 开启一个VAO管理的第三个属性值
    glEnableVertexAttribArray( 1 );

    glBindBuffer( GL_ARRAY_BUFFER, 0 );
    //    glBindVertexArray(0);

    bool success;
    m_shaderProgram.addShaderFromSourceFile( QOpenGLShader::Vertex, ":shaders/shapes.vert" );
    m_shaderProgram.addShaderFromSourceFile( QOpenGLShader::Fragment, ":shaders/shapes.frag" );
    success = m_shaderProgram.link();
    if ( !success ) {
        qDebug() << "ERR:" << m_shaderProgram.log();
    }

    // glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);// 设置线条填充
    glGenBuffers( 1, &EBO );
    glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, EBO );
    glBufferData( GL_ELEMENT_ARRAY_BUFFER, sizeof( indices ), indices, GL_STATIC_DRAW );
    //    glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 );// 这里注释掉，后面的画图的地方可以直接nullptr

    glEnable( GL_BLEND );
    glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); // 打开透明通道

    m_textureWall  = new QOpenGLTexture( QImage( ":/images/images/wall.jpg" ).mirrored() );
    m_textureFreep = new QOpenGLTexture( QImage( ":/images/images/freep.jpg" ).mirrored() );
    m_textureSamll = new QOpenGLTexture( QImage( ":/images/images/small.jpg" ).mirrored() );

    m_shaderProgram.bind(); // gpu端分开两个uniform，这里用的是shaps文件中对应的变量
    m_shaderProgram.setUniformValue( "textureWall", 0 );
    m_shaderProgram.setUniformValue( "textureFreep", 1 );
    m_shaderProgram.setUniformValue( "textureSmall", 2 );
    glBindVertexArray( 0 );

    //        glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT );// 上下镜像
    //        glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT );
    //        glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT ); // 上下重复
    //        glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER ); // 上下重复
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER );

    m_textureFreep->bind( 1 );
    //    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
    //    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
    //    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); // 放大
    //    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); // 缩小
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR ); // 远-》近
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );              // 近-》远

    //    // 相机不需要每个点都设置，所以放到这里
    //    QMatrix4x4 projection;
    //    // vec越大，图越小，视角问题；ration图形显示的额比例和区域，和glViewport一起用比较好
    //    projection.perspective( fov, float( width() / ( 1.0 * height() ) ), 0.1, 100 );
    //    m_shaderProgram.setUniformValue( "projection", projection );
}

void MyOpenGLWidget::resizeGL( int w, int h ) {
    Q_UNUSED( w );
    Q_UNUSED( h );
    //    glViewport( 0, 0, w, h );
}

void MyOpenGLWidget::paintGL() {
    //    float      time = QTime::currentTime().msec();
    QMatrix4x4 model; // 模型
    QMatrix4x4 view;  // 视角

    //    model.rotate( time, 1.0f, 5.0f, 0.5f ); // 第一个参数角度、后三个旋转轴
    //    view.translate( 0.0, 0.0, -3.0 ); // 物体相对于相机的移动
    //    view.lookAt( cameraPos, cameraTarget, up );
    //    const float radius = 10.0f;
    float time = m_time.elapsed() / 1000.0;
    //    float       camX   = sin( time ) * radius;
    //    float       camZ   = cos( time ) * radius;
    view.lookAt( cameraPos, cameraPos + cameraFront, up );

    //    QMatrix4x4   matrix;
    //    matrix.rotate( time, 0.0f, 0.0f, 1.0f );
    //    matrix.translate( 0.5, -0.5f, 0.0f ); // 先旋转后位移
    //    matrix.rotate( time, 0.0f, 0.0f, 1.0f );

    glClearColor( 0.2f, 0.3f, 0.3f, 1.0f );
    glEnable( GL_DEPTH_TEST ); // 点的深度信息，每次清空
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
    //    glUseProgram( shaderProgram ); // 使用着色器
    m_shaderProgram.bind();

    // 相机不需要每个点都设置，所以放到这里
    QMatrix4x4 projection;
    // vec越大，图越小，视角问题；ration图形显示的额比例和区域，和glViewport一起用比较好
    projection.perspective( fov, float( width() / ( 1.0 * height() ) ), 0.1, 100 );
    m_shaderProgram.setUniformValue( "projection", projection );

    m_shaderProgram.setUniformValue( "ratio", ratio );

    glBindVertexArray( VAO ); // 绑定的画图对象

    switch ( m_shape ) {
    case Shape::Rect: {
        //        glActiveTexture( GL_TEXTURE0 ); //已经封装了，不需要                             //
        //        指定激活的通道进行绑定
        m_textureWall->bind( 0 ); //步骤二 默认绑定的是0
                                  //        glActiveTexture( GL_TEXTURE1 );
        m_textureFreep->bind( 1 );
        m_textureSamll->bind( 2 ); // 这里是cpu端绑定
                                   //        m_shaderProgram.setUniformValue( "theMatrix", matrix );

        m_shaderProgram.setUniformValue( "view", view );

        //        glDrawElements( GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr ); // 画矩形

        foreach ( auto item, cubePositions ) {
            model.setToIdentity(); // 每次进来都是单位矩阵
            model.translate( item );
            model.rotate( time, 1.0f, 5.0f, 0.5f );

            m_shaderProgram.setUniformValue( "model", model );
            glDrawArrays( GL_TRIANGLES, 0, 36 ); // 画三维
        }
        break;
    }
    default:
        break;
    }
}

void MyOpenGLWidget::keyPressEvent( QKeyEvent *event ) {
    float cameraSpeed = 2.5 * deltaTime;
    switch ( event->key() ) {
    case Qt::Key_Up: {
        ratio += 0.1;
        break;
    }
    case Qt::Key_Down: {
        ratio -= 0.1;
        break;
    }
    case Qt::Key_W: {
        cameraPos += cameraSpeed * cameraFront;
        break;
    }
    case Qt::Key_S: {
        cameraPos -= cameraSpeed * cameraFront;
        break;
    }
    case Qt::Key_D: {
        cameraPos += cameraSpeed * cameraRight;
        break;
    }
    case Qt::Key_A: {
        cameraPos -= cameraSpeed * cameraRight;
        break;
    }
    default:
        break;
    }
    if ( ratio > 1 ) {
        ratio = 1;
    }
    if ( ratio < 0 ) {
        ratio = 0;
    }

    m_shaderProgram.bind();
    m_shaderProgram.setUniformValue( "ratio", ratio );
    update();
}

void MyOpenGLWidget::mouseMoveEvent( QMouseEvent *event ) {
    static float  yaw   = -90;
    static float  pitch = 0;
    static QPoint lastPos( width() / 2, height() / 2 );
    auto          currentPos = event->pos();
    deltaPos                 = currentPos - lastPos;
    lastPos                  = currentPos;
    float sensitivity        = 0.1f; // 改变敏感度
    deltaPos *= sensitivity;
    yaw += deltaPos.x();
    pitch -= deltaPos.y();
    if ( pitch > 90.0f ) {
        pitch = 89.0f;
    }
    if ( pitch < -89.0f ) {
        pitch = -89.0f;
    }
    cameraFront.setX( cos( yaw * PI / 180 ) * cos( pitch * PI / 180 ) );
    cameraFront.setY( sin( pitch * PI / 180 ) );
    cameraFront.setZ( sin( yaw * PI / 180 ) * cos( pitch * PI / 180 ) );
    cameraFront.normalize();
    update();
}

void MyOpenGLWidget::wheelEvent( QWheelEvent *event ) {
    if ( fov >= 1.0f && fov <= 75.0f ) {
        fov -= event->angleDelta().y() / 120; // 一步是120
    }
    if ( fov <= 1.0f ) {
        fov = 1.0f;
    }
    if ( fov >= 75.0 ) {
        fov = 75.0f;
    }
    update();
}

void MyOpenGLWidget::on_timerout() {
    float currentFrame = m_time.elapsed() / 1000.0f;
    deltaTime          = currentFrame - lastFram;
    lastFram           = currentFrame;
    update();
}
